// Written in the D Programming Language
/*
 * Copyright (c) 2008 OpenGLUtils
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'OpenGLUtils' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
module openglutils.Vector;

import tango.math.Math;
import tango.math.IEEE;
import Float = tango.text.convert.Float;

private struct Vector(int I)
{
    private float[I] data;

    public static Vector!(I) opCall(A...)(A args)
    in
    {
        assert(args.length == I || args.length == 0);
    }
    body
    {
        Vector!(I) v;
        v.zero(); // Initialize to zero because the args might be empty
        foreach(i, arg ; args)
        {
            v.data[i] = arg;
        }
        return v;
    }
    unittest
    {
        Vector2 v2 = Vector2();
        assert(isAllSet(v2, 0.0f, 0.0000001f));
        v2 = Vector2(1.0f, 1.0f);
        assert(isAllSet(v2, 1.0f, 0.0000001f));

        Vector3 v3 = Vector3();
        assert(isAllSet(v3, 0.0f, 0.0000001f));
        v3 = Vector3(1.0f, 1.0f, 1.0f);
        assert(isAllSet(v3, 1.0f, 0.0000001f));

        Vector4 v4 = Vector4();
        assert(isAllSet(v4, 0.0f, 0.0000001f));
        v4 = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
        assert(isAllSet(v4,1.0f, 0.0000001f));
    }

    public void zero()
    {
        data[] = 0.0f;
    }
    unittest
    {
        Vector2 v2;
        v2.zero();
        assert(isAllSet(v2, 0.0f, 0.0000001f));

        Vector3 v3;
        v3.zero();
        assert(isAllSet(v3, 0.0f, 0.0000001f));

        Vector4 v4;
        v4.zero();
        assert(isAllSet(v4, 0.0f, 0.0000001f));
    }

    public float x()
    {
        return data[0];
    }
    public float y()
    {
        return data[1];
    }
    static if(I >= 3)
    {
        public float z()
        {
            return data[2];
        }
    }
    static if(I == 4)
    {
        public float w()
        {
            return data[3];
        }
    }
    unittest
    {
        Vector2 v2;
        v2.data[0] = 2.0f; v2.data[1] = 2.0f;
        assert(assertEquals(v2.x(), 2.0f, 0.0000001f));
        assert(assertEquals(v2.y(), 2.0f, 0.0000001f));

        Vector3 v3;
        v3.data[0] = 3.0f; v3.data[1] = 3.0f; v3.data[2] = 3.0f;
        assert(assertEquals(v3.x(), 3.0f, 0.0000001f));
        assert(assertEquals(v3.y(), 3.0f, 0.0000001f));
        assert(assertEquals(v3.z(), 3.0f, 0.0000001f));

        Vector4 v4;
        v4.data[0] = 4.0f; v4.data[1] = 4.0f; v4.data[2] = 4.0f; v4.data[3] = 4.0f;
        assert(assertEquals(v4.x(), 4.0f, 0.0000001f));
        assert(assertEquals(v4.y(), 4.0f, 0.0000001f));
        assert(assertEquals(v4.z(), 4.0f, 0.0000001f));
        assert(assertEquals(v4.w(), 4.0f, 0.0000001f));
    }

    public void x(float _x)
    {
        data[0] = _x;
    }
    public void y(float _y)
    {
        data[1] = _y;
    }
    static if(I >= 3)
    {
        public void z(float _z)
        {
            data[2] = _z;
        }
    }
    static if(I == 4)
    {
        public void w(float _w)
        {
            data[3] = _w;
        }
    }
    unittest
    {
        Vector2 v2;
        v2.x(1.0f);
        v2.y(1.0f);
        assert(isAllSet(v2, 1.0f, 0.0000001f));

        Vector3 v3;
        v3.x(2.0f);
        v3.y(2.0f);
        v3.z(2.0f);
        assert(isAllSet(v3, 2.0f, 0.0000001f));

        Vector4 v4;
        v4.x(3.0f);
        v4.y(3.0f);
        v4.z(3.0f);
        v4.w(3.0f);
        assert(isAllSet(v4, 3.0f, 0.0000001f));
    }

    public void set(A...)(A args)
    in
    {
        assert(I == args.length);
    }
    body
    {
        foreach(i, arg ; args)
        {
            data[i] = arg;
        }
    }
    unittest
    {
        Vector2 v2;
        v2.set(2.0f, 2.0f);
        assert(isAllSet(v2, 2.0f, 0.0000001f));

        Vector3 v3;
        v3.set(3.0f, 3.0f, 3.0f);
        assert(isAllSet(v3, 3.0f, 0.0000001f));

        Vector4 v4;
        v4.set(4.0f, 4.0f, 4.0f, 4.0f);
        assert(isAllSet(v4, 4.0f, 0.0000001f));
    }

    public float lengthSquared()
    {
        float ret = 0.0f;
        foreach (value ; data)
        {
            ret += value * value;
        }
        return ret;
    }
    unittest
    {
        Vector2 v2;
        v2.set(2.0f, 2.0f);
        assert(assertEquals(v2.lengthSquared(), 8.0f, 0.0000001f));

        Vector3 v3;
        v3.set(2.0f, 2.0f, 2.0f);
        assert(assertEquals(v3.lengthSquared(), 12.0f, 0.0000001f));

        Vector4 v4;
        v4.set(2.0f, 2.0f, 2.0f, 2.0f);
        assert(assertEquals(v4.lengthSquared(), 16.0f, 0.0000001f));
    }

    public float length()
    {
        return sqrt(lengthSquared());
    }
    unittest
    {
        Vector2 v2;
        v2.set(4.0f, 3.0f);
        assert(assertEquals(v2.length(), 5.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 2.0f, 2.0f);
        assert(assertEquals(v3.length(), 3.0f, 0.0000001f));

        Vector4 v4;
        v4.set(2.0f, 2.0f, 2.0f, 2.0f);
        assert(assertEquals(v4.length(), 4.0f, 0.0000001f));
    }

    public void negate()
    {
        foreach (inout value ; data)
        {
            value = -value;
        }
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        v2.negate();
        assert(isAllSet(v2, -1.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        v3.negate();
        assert(isAllSet(v3, -1.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        v4.negate();
        assert(isAllSet(v4, -1.0f, 0.0000001f));
    }

    public float dot(Vector!(I) v2)
    {
        float ret = 0.0f;
        foreach (i, value ; data)
        {
            ret += value * v2.data[i];
        }
        return ret;
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(2.0f, 2.0f);
        assert(assertEquals(v2.dot(v2b), 4.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(2.0f, 2.0f, 2.0f);
        assert(assertEquals(v3.dot(v3b), 6.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(2.0f, 2.0f, 2.0f, 2.0f);
        assert(assertEquals(v4.dot(v4b), 8.0f, 0.0000001f));
    }

    public float angle(Vector!(I) v2)
    {
        float dls = dot(v2) / (length() * v2.length());
        if (dls < -1.0f)
        {
            dls = -1.0f;
        }
        else if (dls > 1.0f)
        {
            dls = 1.0f;
        }
        return cast(float)acos(dls);
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(2.0f, 2.0f);
        assert(assertEquals(v2.angle(v2b), 0.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(2.0f, 2.0f, 2.0f);
        assert(assertEquals(v3.angle(v3b), 0.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(2.0f, 2.0f, 2.0f, 2.0f);
        assert(assertEquals(v4.angle(v4b), 0.0f, 0.0000001f));
    }

    public void translate(A...)(A args)
    in
    {
        assert(args.length == I);
    }
    body
    {
        foreach(i, arg ; args)
        {
            data[i] += arg;
        }
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        v2.translate(2.0f, 2.0f);
        assert(isAllSet(v2, 3.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        v3.translate(2.0f, 2.0f, 2.0f);
        assert(isAllSet(v3, 3.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        v4.translate(2.0f, 2.0f, 2.0f, 2.0f);
        assert(isAllSet(v4, 3.0f, 0.0000001f));
    }

    public void normalize()
    {
        float len = length();
        if (len != 0.0f)
        {
            return scale(1.0f / len);
        }
        throw new Exception("Zero length vector.");
    }
    unittest
    {
        Vector2 v2;
        v2.set(34.0f, 324.0f);
        v2.normalize();
        assert(assertEquals(v2.length(), 1.0f, 0.000001f));

        Vector3 v3;
        v3.set(34.0f, 324.0f, 432.0f);
        v3.normalize();
        assert(assertEquals(v3.length(), 1.0f, 0.000001f));

        Vector4 v4;
        v4.set(34.0f, 324.0f, 432.0f, 4.43f);
        v4.normalize();
        assert(assertEquals(v4.length(), 1.0f, 0.000001f));
    }

    public void scale(float scale)
    {
        foreach (inout value ; data)
        {
            value *= scale;
        }
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        v2.scale(3.0f);
        assert(isAllSet(v2, 3.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        v3.scale(3.0f);
        assert(isAllSet(v3, 3.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        v4.scale(3.0f);
        assert(isAllSet(v4, 3.0f, 0.0000001f));
    }

    public bool equals(Vector!(I) v2)
    {
        foreach (i, value ; data)
        {
            if (!assertEquals(value, v2.data[i], 0.0000001f))
            {
                return false;
            }
        }
        return true;
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(1.0f, 1.0f);
        assert(v2.equals(v2b));
        v2b.y(23.0f);
        assert(!v2.equals(v2b));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(1.0f, 1.0f, 1.0f);
        assert(v3.equals(v3b));
        v3b.y(23.0f);
        assert(!v3.equals(v3b));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(1.0f, 1.0f, 1.0f, 1.0f);
        assert(v4.equals(v4b));
        v4b.y(23.0f);
        assert(!v4.equals(v4b));
    }

    public Vector!(I) opAdd(Vector!(I) v2)
    {
        Vector!(I) ret;
        foreach (i, value ; data)
        {
            ret.data[i] = value + v2.data[i];
        }
        return ret;
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(2.0f, 2.0f);
        Vector2 result2 = v2 + v2b;
        assert(isAllSet(result2, 3.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(2.0f, 2.0f, 2.0f);
        Vector3 result3 = v3 + v3b;
        assert(isAllSet(result3, 3.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(2.0f, 2.0f, 2.0f, 2.0f);
        Vector4 result4 = v4 + v4b;
        assert(isAllSet(result4, 3.0f, 0.0000001f));
    }

    public void opAddAssign(Vector!(I) v2)
    {
        foreach (i, inout value ; data)
        {
            value += v2.data[i];
        }
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(2.0f, 2.0f);
        v2 += v2b;
        assert(isAllSet(v2, 3.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(2.0f, 2.0f, 2.0f);
        v3 += v3b;
        assert(isAllSet(v3, 3.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(2.0f, 2.0f, 2.0f, 2.0f);
        v4 += v4b;
        assert(isAllSet(v4, 3.0f, 0.0000001f));
    }

    public Vector!(I) opSub(Vector!(I) v2)
    {
        Vector!(I) ret;
        foreach (i, value ; data)
        {
            ret.data[i] = value - v2.data[i];
        }
        return ret;
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(2.0f, 2.0f);
        Vector2 result2 = v2 - v2b;
        assert(isAllSet(result2, -1.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(2.0f, 2.0f, 2.0f);
        Vector3 result3 = v3 - v3b;
        assert(isAllSet(result3, -1.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(2.0f, 2.0f, 2.0f, 2.0f);
        Vector4 result4 = v4 - v4b;
        assert(isAllSet(result4, -1.0f, 0.0000001f));
    }

    public void opSubAssign(Vector!(I) v2)
    {
        foreach (i, inout value ; data)
        {
            value -= v2.data[i];
        }
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b;
        v2b.set(2.0f, 2.0f);
        v2 -= v2b;
        assert(isAllSet(v2, -1.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b;
        v3b.set(2.0f, 2.0f, 2.0f);
        v3 -= v3b;
        assert(isAllSet(v3, -1.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b;
        v4b.set(2.0f, 2.0f, 2.0f, 2.0f);
        v4 -= v4b;
        assert(isAllSet(v4, -1.0f, 0.0000001f));
    }

    public Vector!(I) opMul(float mul)
    {
        Vector!(I) ret;
        foreach (i, inout value ; ret.data)
        {
            value = data[i];
        }
        ret.scale(mul);
        return ret;
    }
    unittest
    {
        Vector2 v2;
        v2.set(1.0f, 1.0f);
        Vector2 v2b = v2 * 3.0f;
        assert(isAllSet(v2b, 3.0f, 0.0000001f));
        assert(isAllSet(v2, 1.0f, 0.0000001f));

        Vector3 v3;
        v3.set(1.0f, 1.0f, 1.0f);
        Vector3 v3b = v3 * 3.0f;
        assert(isAllSet(v3b, 3.0f, 0.0000001f));
        assert(isAllSet(v3, 1.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b = v4 * 3.0f;
        assert(isAllSet(v4b, 3.0f, 0.0000001f));
        assert(isAllSet(v4, 1.0f, 0.0000001f));
    }

    public void opMulAssign(float mul)
    {
        scale(mul);
    }
    unittest
    {
        Vector2 v2 = Vector2(2.0f, 2.0f);
        v2 *= 2.0f;
        assert(isAllSet(v2, 4.0f, 0.0000001f));

        Vector3 v3 = Vector3(2.0f, 2.0f, 2.0f);
        v3 *= 2.0f;
        assert(isAllSet(v3, 4.0f, 0.0000001f));

        Vector4 v4 = Vector4(2.0f, 2.0f, 2.0f, 2.0f);
        v4 *= 2.0f;
        assert(isAllSet(v4, 4.0f, 0.0000001f));
    }

    public Vector!(I) opDiv(float div)
    {
        if (div == 0.0f || isNaN(div) != 0)
        {
            throw new Exception("Invalid value for divisor: '" ~ Float.toString(div) ~ "'");
        }
        Vector!(I) ret;
        float inv = 1.0f / div;
        foreach (i, inout value ; ret.data)
        {
            value = data[i] * inv;
        }
        return ret;
    }
    unittest
    {
        Vector2 v2;
        v2.set(4.0f, 4.0f);
        Vector2 v2b = v2 / 2.0f;
        assert(isAllSet(v2b, 2.0f, 0.0000001f));

        Vector3 v3;
        v3.set(6.0f, 6.0f, 6.0f);
        Vector3 v3b = v3 / 3.0f;
        assert(isAllSet(v3b, 2.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        Vector4 v4b = v4 / 2.0f;
        assert(isAllSet(v4b, 0.5f, 0.0000001f));
    }

    public void opDivAssign(float div)
    {
        if (div == 0.0f || isNaN(div) != 0)
        {
            throw new Exception("Invalid value for divisor: '" ~ Float.toString(div) ~ "'");
        }
        scale(1.0f / div);
    }
    unittest
    {
        Vector2 v2;
        v2.set(4.0f, 4.0f);
        v2 /= 2.0f;
        assert(isAllSet(v2, 2.0f, 0.0000001f));

        Vector3 v3;
        v3.set(6.0f, 6.0f, 6.0f);
        v3 /= 3.0f;
        assert(isAllSet(v3, 2.0f, 0.0000001f));

        Vector4 v4;
        v4.set(1.0f, 1.0f, 1.0f, 1.0f);
        v4 /= 2.0f;
        assert(isAllSet(v4, 0.5f, 0.0000001f));
    }

    static if(I == 3)
    {
        public Vector!(3) cross(Vector!(3) v2)
        {
            Vector!(3) ret;
            ret.data[0] = y() * v2.z() - z() * v2.y();
            ret.data[1] = v2.x() * z() - v2.z() * x();
            ret.data[2] = x() * v2.y() - y() * v2.x();
            return ret;
        }
        unittest
        {
            Vector3 v3;
            v3.set(1.0f, 1.0f, 1.0f);
            Vector3 v3b;
            v3b.set(2.0f, 2.0f, 2.0f);
            Vector3 v3c = v3.cross(v3b);
            assert(isAllSet(v3c, 0.0f, 0.0000001f));
        }
    }
/+
    public Vector!(I) transform(Matrix!(I) m)
    {
        static if(is(typeof(v) == Vector2))
        {
            float _x = m[0][0] * v.x() + m[1][0] * v.y();
            float _y = m[0][1] * v.x() + m[1][1] * v.y();
            v.x(_x);
            v.y(_y);
        }
    }
    public alias _transform!(2) transform;
    unittest
    {
        Vector2 v2;
        v2.set(2.0f, 2.0f);
        Matrix2 m2;
        m2.set(1.0f, 2.0f, 3.0f, 4.0f);
        v2.transform(m2);
        v2.print();
    }
+/
    public float[] toArray()
    {
        float[] array = new float[I];
        array[] = data.dup;
        return array;
    }
    unittest
    {
        Vector2 v2 = Vector2(2.0f, 2.0f);
        float[] array = v2.toArray();
        assert(isAllSet(array, 2.0f, 0.0000001f));
        delete array;

        Vector3 v3 = Vector3(2.0f, 2.0f, 2.0f);
        array = v3.toArray();
        assert(isAllSet(array, 2.0f, 0.0000001f));
        delete array;

        Vector4 v4 = Vector4(2.0f, 2.0f, 2.0f, 2.0f);
        array = v4.toArray();
        assert(isAllSet(array, 2.0f, 0.0000001f));
        delete array;
    }

    public float* ptr()
    out(result)
    {
        assert(result !is null);
    }
    body
    {
        return data.ptr;
    }
    unittest
    {
        Vector2 v2 = Vector2();
        v2.ptr();
    }
}

public alias Vector!(2) Vector2;
public alias Vector!(3) Vector3;
public alias Vector!(4) Vector4;

private bool isAllSet(float[] array, float value, float precision)
{
    foreach(i ; array)
    {
        if (!assertEquals(i, value, precision))
        {
            return false;
        }
    }
    return true;
}

private bool _isAllSet(int I)(Vector!(I) v, float value, float precision)
{
    foreach(i ; v.data)
    {
        if (!assertEquals(i, value, precision))
        {
            return false;
        }
    }
    return true;
}
private alias _isAllSet!(2) isAllSet;
private alias _isAllSet!(3) isAllSet;
private alias _isAllSet!(4) isAllSet;

private bool assertEquals(float x, float y, float precision)
{
    if (x == y)
    {
        return true;
    }
    // TODO: Fix NaN comparisons
    if (x == float.nan)
    {
        return y == float.nan;
    }
    if (y == float.nan)
    {
        return false;
    }
    return abs(x - y) <= precision;
}
unittest
{
	assert (!assertEquals(1.0f, float.nan, 0.01f));
	assert (assertEquals(0.0f, 0.0f, 0.0f));
	assert (!assertEquals(1.0f, 0.0f, 0.0f));
	assert (!assertEquals(float.nan, 0.0f, 0.0f));
	// assert (assertEquals(float.nan, float.nan, 0.0f));
}

private bool isSameArray(int I)(float[I] arr1, float[I] arr2, float precision)
{
    foreach (i, value ; arr1)
    {
        if (!assertEquals(value, arr2[i], precision))
        {
            return false;
        }
    }
    return true;
}
